#include "cpu/irq.h"
#include "cpu/cpu.h"
#include "os_cfg.h"
#include "comm/cpu_instr.h"
#include "tools/log.h"

static gate_desc_t IDT[IDE_TABLE_NR];

void exception_handler_unknown(void);

/*
 * 初始化8259A
 */
static void init_pic(void)
{
    //发送主片ICW
    outb(PIC0_ICW1, PIC_ICW1_ALWAYS_1 | PIC_ICW1_ICW4); // 边沿触发，级联，需要配置icw4, 8086模式
    outb(PIC0_ICW2, IRQ_PIC_START); // 对应的中断号起始序号0x20
    outb(PIC0_ICW3, 1 << 2);    // 主片IRQ2有从片
    outb(PIC0_ICW4, PIC_ICW4_8086); // 普通全嵌套、非缓冲、非自动结束、8086模式

    //发送从片ICW
    outb(PIC1_ICW1, PIC_ICW1_ICW4 | PIC_ICW1_ALWAYS_1); // 边沿触发，级联，需要配置icw4, 8086模式
    outb(PIC1_ICW2, IRQ_PIC_START + 8);    // 起始中断序号，要加上8
    outb(PIC1_ICW3, 2);    // 没有从片，连接到主片的IRQ2上
    outb(PIC1_ICW4, PIC_ICW4_8086);    // 普通全嵌套、非缓冲、非自动结束、8086模式

    outb(PIC0_IMR, 0xFF & ~(1 << 2));   // 屏蔽主片所有引脚的中断（除了级联从片引脚）
    outb(PIC1_IMR, 0xFF);               // 屏蔽从片所有引脚的中断
}

void irq_init(void)
{
    // 先给所有中断向量号安装一个默认中断门
    for (int i = 0; i < sizeof IDT / sizeof(gate_desc_t); i++) {
        gate_desc_set(IDT + i, KERNEL_SELECTOR_CS, 
                    (uint32_t)exception_handler_unknown, 
                    GATE_P_PRESENT | GATE_DPL0 | GATE_TYPE_IDT);
    }

    irq_install(IRQ0_DE, exception_handler_divider);
    irq_install(IRQ1_DB, exception_handler_Debug);
    irq_install(IRQ2_NMI, exception_handler_NMI);
	irq_install(IRQ3_BP, exception_handler_breakpoint);
	irq_install(IRQ4_OF, exception_handler_overflow);
	irq_install(IRQ5_BR, exception_handler_bound_range);
	irq_install(IRQ6_UD, exception_handler_invalid_opcode);
	irq_install(IRQ7_NM, exception_handler_device_unavailable);
	irq_install(IRQ8_DF, exception_handler_double_fault);
	irq_install(IRQ10_TS, exception_handler_invalid_tss);
	irq_install(IRQ11_NP, exception_handler_segment_not_present);
	irq_install(IRQ12_SS, exception_handler_stack_segment_fault);
	irq_install(IRQ13_GP, exception_handler_general_protection);
	irq_install(IRQ14_PF, exception_handler_page_fault);
	irq_install(IRQ16_MF, exception_handler_fpu_error);
	irq_install(IRQ17_AC, exception_handler_alignment_check);
	irq_install(IRQ18_MC, exception_handler_machine_check);
	irq_install(IRQ19_XM, exception_handler_smd_exception);
	irq_install(IRQ20_VE, exception_handler_virtual_exception);
	irq_install(IRQ21_CP, exception_handler_control_exception);

    lidt((uint32_t)IDT, sizeof IDT);
    init_pic();
}

static void dump_core_regs(exception_frame_t *frame)
{
    uint32_t esp, ss;
    // 发生异常之前是3特权级
    if (frame->cs & 0x03) {
        esp = frame->esp3;
        ss = frame->ss3;
    } 
    // 否则发生异常之前是0特权级
    else {
        esp = frame->esp;
        ss = frame->ds;
    }
    log_printf("IRQ: %x, error_code: %x", frame->num, frame->error_code);
    log_printf("cs: %x ds: %x es: %x ss: %x fs: %x gs: %x", 
                frame->cs, frame->ds, frame->es, ss, frame->fs, frame->gs);
    log_printf(
        "eax: %x ebx: %x ecx: %x edx: %x\r\n"
        "edi: %x esi: %x ebp: %x esp: %x\r\n",
        frame->eax, frame->ebx, frame->ecx, frame->edx,
        frame->edi, frame->esi, frame->ebp, esp
    );
    log_printf("eip: %x eflags: %x\r\n", frame->eip, frame->eflags);
}

static void do_default_handler(exception_frame_t *p_excep_frame, const char *msg)
{
    log_printf("------------------------");
    log_printf("IRQ/Exception happend: %s", msg);
    dump_core_regs(p_excep_frame);

    while(1) {
        hlt();
    }
}

void do_handler_unknown(exception_frame_t *p_excep_frame)
{
    do_default_handler(p_excep_frame, "Unknown exception");
}

void do_handler_divider(exception_frame_t *p_excep_frame)
{
    do_default_handler(p_excep_frame, "Divider exception");
}

void do_handler_Debug(exception_frame_t * frame) 
{
	do_default_handler(frame, "Debug Exception");
}

void do_handler_NMI(exception_frame_t * frame)
{
	do_default_handler(frame, "NMI Interrupt.");
}

void do_handler_breakpoint(exception_frame_t * frame) 
{
	do_default_handler(frame, "Breakpoint.");
}

void do_handler_overflow(exception_frame_t * frame) 
{
	do_default_handler(frame, "Overflow.");
}

void do_handler_bound_range(exception_frame_t * frame) 
{
	do_default_handler(frame, "BOUND Range Exceeded.");
}

void do_handler_invalid_opcode(exception_frame_t * frame) 
{
	do_default_handler(frame, "Invalid Opcode.");
}

void do_handler_device_unavailable(exception_frame_t * frame) 
{
	do_default_handler(frame, "Device Not Available.");
}

void do_handler_double_fault(exception_frame_t * frame) 
{
	do_default_handler(frame, "Double Fault.");
}

void do_handler_invalid_tss(exception_frame_t * frame) 
{
	do_default_handler(frame, "Invalid TSS");
}

void do_handler_segment_not_present(exception_frame_t * frame) 
{
	do_default_handler(frame, "Segment Not Present.");
}

void do_handler_stack_segment_fault(exception_frame_t * frame) 
{
	do_default_handler(frame, "Stack-Segment Fault.");
}

void do_handler_general_protection(exception_frame_t * frame) 
{
    log_printf("--------------------------------");
    log_printf("IRQ/Exception happend: General Protection.");
    if (frame->error_code & ERR_EXT) {
        log_printf("the exception occurred during delivery of an "
                "event external to the program, such as an interrupt"
                "or an earlier exception.");
    } else {
        log_printf("the exception occurred during delivery of a"
                    "software interrupt (INT n, INT3, or INTO).");
    }
    
    if (frame->error_code & ERR_IDT) {
        log_printf("the index portion of the error code refers "
                    "to a gate descriptor in the IDT");
    } else {
        log_printf("the index refers to a descriptor in the GDT");
    }
    
    log_printf("segment index: %d", frame->error_code & 0xFFF8);

    dump_core_regs(frame);
    
    while(1) {
        hlt();
    }
}

void do_handler_page_fault(exception_frame_t * frame) 
{
    log_printf("--------------------------------");
    log_printf("IRQ/Exception happend: Page fault.");

    if (frame->error_code & ERR_PAGE_P) {
        log_printf("\tpage-level protection violation: 0x%x.", read_cr2());
    } else {
        log_printf("\tPage doesn't present 0x%x", read_cr2());
    }
    
    if (frame->error_code & ERR_PAGE_WR) {
        log_printf("\tThe access causing the fault was a read.");
    } else {
        log_printf("\tThe access causing the fault was a write.");
    }
    
    if (frame->error_code & ERR_PAGE_US) {
        log_printf("\tA supervisor-mode access caused the fault.");
    } else {
        log_printf("\tA user-mode access caused the fault.");
    }

    dump_core_regs(frame);
    
    while(1) {
        hlt();
    }
}

void do_handler_fpu_error(exception_frame_t * frame) {
	do_default_handler(frame, "X87 FPU Floating Point Error.");
}

void do_handler_alignment_check(exception_frame_t * frame) {
	do_default_handler(frame, "Alignment Check.");
}

void do_handler_machine_check(exception_frame_t * frame) {
	do_default_handler(frame, "Machine Check.");
}

void do_handler_smd_exception(exception_frame_t * frame) {
	do_default_handler(frame, "SIMD Floating Point Exception.");
}

void do_handler_virtual_exception(exception_frame_t * frame) {
	do_default_handler(frame, "Virtualization Exception.");
}

void do_handler_control_exception(exception_frame_t * frame) {
	do_default_handler(frame, "Control Exception.");
}

int irq_install(int num, irq_handler_t handler)
{
    if (num > IDE_TABLE_NR)
        return -1;

    gate_desc_set(IDT + num, KERNEL_SELECTOR_CS, (uint32_t)handler,  
                GATE_P_PRESENT | GATE_DPL0 | GATE_TYPE_IDT);

    return 0;
}

void irq_disable_global(void)
{
    cli();
}

void irq_enable_global(void)
{
    sti();
}

void irq_enable(int irq_num)
{
    if (irq_num < IRQ_PIC_START)
        return;

    int irq_pin = irq_num - IRQ_PIC_START;  // 根据中断号得到8259A芯片的引脚号

    // 主片
    if (irq_pin < 8) {
        uint8_t mask = inb(PIC0_IMR) & ~(1 << irq_pin);
        outb(PIC0_IMR, mask);
    } 
    // 从片
    else {
        irq_pin -= 8;
        uint8_t mask = inb(PIC1_IMR) & ~(1 << irq_pin);
        outb(PIC1_IMR, mask);
    }
}

void irq_disable(int irq_num)
{
    if (irq_num < IRQ_PIC_START)
        return;

    int irq_pin = irq_num - IRQ_PIC_START;  // 根据中断号得到8259A芯片的引脚号

    // 主片
    if (irq_pin < 8) {
        uint8_t mask = inb(PIC0_IMR) | (1 << irq_pin);
        outb(PIC0_IMR, mask);
    } 
    // 从片
    else {
        irq_pin -= 8;
        uint8_t mask = inb(PIC1_IMR) | (1 << irq_pin);
        outb(PIC1_IMR, mask);
    }
}

void pic_send_eoi(int irq_num)
{
    int irq_pin = irq_num - IRQ_PIC_START;

    // 如果是从片引脚输入的中断，则需要给从片发送EOI
    if (irq_pin >= 8) 
        outb(PIC1_OCW2, PIC_OCW2_EOI);

    // 给主片发送EOI
    outb(PIC0_OCW2, PIC_OCW2_EOI);
}

/**
 * 进入临界区
 */
irq_state_t irq_enter_protection(void)
{
    irq_state_t state = read_eflags();
    irq_disable_global();
    return state;
}

/**
 * 离开临界区
 */
void irq_leave_protection(irq_state_t state)
{
    write_eflags(state);
}